package com.ap.core.domain;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

import javax.persistence.Embeddable;
import java.io.Serializable;
import java.math.BigDecimal;

/**
 * This represents a monetary amount with zero precision loss.
 */
@Embeddable
public final class Amount implements Serializable {

    /** Generated serial version UID */
    private static final int DECIMAL_PRECISION = 2;

    /** {@link java.math.BigDecimal} value for the {@link com.ap.core.domain.Amount} */
    private BigDecimal value;


    private Amount() {
        // Do nothing constructor to keep hibernate happy
    }

    /**
     * Instantiates a new amount.
     * 
     * @param newValue
     *            the new {@link java.math.BigDecimal} value for the amount
     */
    public Amount(final BigDecimal newValue) {
        this.value = newValue;
    }

    /**
     * Create a new instance of the amount
     * 
     * @param value
     *            the {@link java.math.BigDecimal} value for the amount
     * @return the created {@link com.ap.core.domain.Amount}
     */
    public static Amount valueOf(final BigDecimal value) {
        return new Amount(value);
    }

    /**
     * Instantiates a new amount using the value of the Amount passed to the constructor
     * 
     * @param amount
     *            the amount to use
     */
    public Amount(final Amount amount) {
        if (amount != null) {
            this.value = amount.getValue();
        }
    }


    public int hashCode() {
        return new HashCodeBuilder().append(this.value).toHashCode();
    }

    /**
     * Note that for the purposes of this 20.0 is NOT EQUAL TO 20.00.
     */
    public boolean equals(final Object obj) {
        boolean retVal = false;
        if (obj != null) {
            if (obj == this) {
                retVal = true;
            } else {
                retVal = obj.getClass() == getClass() && new EqualsBuilder().append(this.value, ((Amount) obj).value).isEquals();
            }
        }
        return retVal;
    }


    public String toString() {
        return value.toString();
    }

    // - - - - - - add, subtract, is less than, is greater than, is less than equal to, is greater than equal to - - - - -

    /**
     * Add the value of the provided amount to the current instance
     * 
     * @param amount
     *            the {@link com.ap.core.domain.Amount} to add
     * @return the updated {@link com.ap.core.domain.Amount}
     */
    public Amount add(final Amount amount) {
        if (amount != null) {
            final BigDecimal result = value.add(amount.getValue());
            return new Amount(result);
        } 
        return null;
    }

    /**
     * Subtract the value of the provided amount from the current instance
     * 
     * @param amount
     *            the {@link com.ap.core.domain.Amount} to subtract
     * @return the updated {@link com.ap.core.domain.Amount}
     */
    public Amount subtract(final Amount amount) {
        if (amount != null) {
            final BigDecimal result = value.subtract(amount.getValue());
            return new Amount(result);
        } 
        return null;
    }

    /**
     * 
     * Checks whether the current amount is less than the amount passed as parameter to the method
     * 
     * @param amount
     *            the {@link com.ap.core.domain.Amount} to be compared against
     * @return true, if the current amount is less than the {@link com.ap.core.domain.Amount} passed as the parameter to the method
     */
    public boolean isLessThan(final Amount amount) {
        return amount == null ? false : value.compareTo(amount.getValue()) < 0;
    }

    /**
     * 
     * Checks whether the current amount is greater than the amount passed as parameter to the method
     * 
     * @param amount
     *            the {@link com.ap.core.domain.Amount} to be compared against
     * @return true, if the current amount is greater than the {@link com.ap.core.domain.Amount} passed as the parameter to the method
     */
    public boolean isGreaterThan(final Amount amount) {
        return amount == null ? false : value.compareTo(amount.getValue()) > 0;
    }

    /**
     * 
     * Checks whether the current amount is less than or equal to the amount passed a parameter to the method
     * 
     * @param amount
     *            the {@link com.ap.core.domain.Amount} to be compared against
     * @return true, if the current is less than or equal to the {@link com.ap.core.domain.Amount} passed as the parameter to the method
     */
    public boolean isLessThanOrEqualTo(final Amount amount) {
        return amount == null ? false : value.compareTo(amount.getValue()) <= 0;
    }

    /**
     * 
     * Check whether the current amount is greater than or equal to the amount passed as parameter to the method
     * 
     * @param amount
     *            the {@link com.ap.core.domain.Amount} to be compared against
     * @return true, if the current amount is greater than or equal to the {@link com.ap.core.domain.Amount} passed as the parameter to the method
     */
    public boolean isGreaterThanOrEqualTo(final Amount amount) {
        return amount == null ? false : value.compareTo(amount.getValue()) >= 0;
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    /**
     * Indicates whether this is a valid amount. Checks the scale of the amount is one or two decimal places.
     * 
     * @return True if valid
     */
    public boolean isValid() {
        return value.scale() < 3;
    }

    /**
     * Gets the {@link java.math.BigDecimal} value for the amount
     * 
     * @return the {@link java.math.BigDecimal} value
     */
    public BigDecimal getValue() {
        return value;
    }

    /**
     * Gets the {@link java.math.BigDecimal} value for the amount
     * 
     * @return the {@link java.math.BigDecimal} value
     */
    public BigDecimal getFormatedValue() {
        return value.setScale(DECIMAL_PRECISION);
    }
}
